package com.yqg.protection.listenern;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yqg.protection.common.config.WebSocketConfig;
import com.yqg.protection.entity.RecordCheck;
import com.yqg.protection.entity.User;
import com.yqg.protection.service.IRecordCheckService;
import com.yqg.protection.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ServerEndpoint 注解是一个类层次的注解，它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint(value = "/WebSocket/{userId}",configurator = WebSocketConfig.class)
@Component
@Slf4j
public class NoticeWebsocket {

    //记录连接的客户端
    public static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * userId关联sid（解决同一用户id，在多个web端连接的问题）
     */
    public static Map<Long, Set<String>> conns = new ConcurrentHashMap<>();

    private String sid = null;

    private Long userId;


    private static IUserService userService;

    private static IRecordCheckService recordCheckService;

    @Autowired
    public void setUserService(IUserService userService){
        this.userService = userService;
    }

    @Autowired
    public void setRecordCheckService(IRecordCheckService recordCheckService) {this.recordCheckService = recordCheckService;}

    /**
     * 连接成功后调用的方法
     *
     * @param session
     * @param userId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") Long userId) {
        String userAgent = getHeader(session, "User-Agent");
        String ipAddress = getHeader(session, "ipAddress");
        boolean isMobileClient = userAgent != null && (userAgent.contains("Android") || userAgent.contains("iPhone") || userAgent.contains("iPad"));
        String clientType;
        if (isMobileClient) {
            clientType = "app--";
        } else {
            clientType = "web--";
        }
        this.sid = clientType + UUID.randomUUID().toString();
        this.userId = userId;
        clients.put(this.sid, session);

        Set<String> clientSet = conns.get(userId);
        if (clientSet == null) {
            clientSet = new HashSet<>();
            conns.put(userId, clientSet);
        }
        clientSet.add(this.sid);
        log.info(this.sid + "连接开启！");
        User user = userService.getUserById(userId);
        user.setIsOnline("0");
        StringBuilder onlineWay = new StringBuilder("");
        for (String client:clientSet) {
            if (client.contains("app--") && !onlineWay.toString().contains("app/")) {
                onlineWay.append("app/");
            } else if (client.contains("web--") && !onlineWay.toString().contains("web/")) {
                onlineWay.append("web/");
            }
        }
        String onlineWayStr = onlineWay.substring(0, onlineWay.length() - 1);
        user.setOnlineWay(onlineWayStr);
        if (user.getSuperviLength() != null && user.getSuperviLength() != 0) {
            user.setSuperviLength(user.getSuperviLength() + (System.currentTimeMillis() - user.getLastLoginTime()));
        } else {
            user.setSuperviLength(0l);
        }
        user.setLastLoginTime(System.currentTimeMillis());
        userService.saveOrUpdate(user);
        LambdaQueryWrapper<RecordCheck> recordCheckQueryWrapper = new LambdaQueryWrapper<>();
        recordCheckQueryWrapper.eq(RecordCheck::getUserId, user.getId());
        recordCheckQueryWrapper.eq(RecordCheck::getIp, ipAddress);
        recordCheckQueryWrapper.orderByDesc(RecordCheck::getStartTime);
        List<RecordCheck> recordChecks = recordCheckService.list(recordCheckQueryWrapper);
        if (recordChecks != null && recordChecks.size() > 0) {
            RecordCheck recordCheck = recordChecks.get(0);
            if(recordCheck.getEndTime() != null && LocalDateTime.now().isAfter(recordCheck.getEndTime().plusMinutes(1))){
                RecordCheck recordCheck1 = new RecordCheck();
                recordCheck1.setUserId(user.getId());
                recordCheck1.setStartTime(LocalDateTime.now());
                recordCheck1.setIp(ipAddress);
                recordCheck1.setState("已查岗");
                recordCheck1.setIsDelete(0);
                recordCheckService.save(recordCheck1);
            } else {
                recordCheck.setEndTime(null);
                recordCheckService.saveOrUpdate(recordCheck);
            }
        } else {
            RecordCheck recordCheck = new RecordCheck();
//            recordCheck.setId(this.sid);
            recordCheck.setUserId(user.getId());
            recordCheck.setStartTime(LocalDateTime.now());
            recordCheck.setIp(ipAddress);
            recordCheck.setState("已查岗");
            recordCheck.setIsDelete(0);
            recordCheckService.save(recordCheck);
        }
    }

    public static String getHeader(Session session, String headerName) {
        final String header = (String) session.getUserProperties().get(headerName);
        if (StrUtil.isBlank(header)) {
            log.error("获取header失败，不安全的链接，即将关闭");
            try {
                session.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return header;
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("userId") Long userId) {
        String ipAddress = getHeader(session, "ipAddress");
        log.info(this.sid + "连接断开！");
        clients.remove(this.sid);
        conns.get(userId).remove(this.sid);
        User user = userService.getUserById(userId);
        if (isServerClose(userId)) {
            user.setIsOnline("1");
            user.setOnlineWay("");
            user.setSuperviLength(user.getSuperviLength() + (System.currentTimeMillis() - user.getLastLoginTime()));
            LambdaQueryWrapper<RecordCheck> recordCheckQueryWrapper = new LambdaQueryWrapper<>();
            recordCheckQueryWrapper.eq(RecordCheck::getUserId, user.getId());
            recordCheckQueryWrapper.eq(RecordCheck::getIp, ipAddress);
            recordCheckQueryWrapper.isNull(RecordCheck::getEndTime);
            RecordCheck recordCheck = recordCheckService.getOne(recordCheckQueryWrapper);
            recordCheck.setEndTime(LocalDateTime.now());
            recordCheckService.saveOrUpdate(recordCheck);
        } else {
            Set<String> clientSet = NoticeWebsocket.clients.keySet();
            user.setIsOnline("0");
            StringBuilder onlineWay = new StringBuilder("");
            for (String client:clientSet) {
                if (client.contains("app--") && !onlineWay.toString().contains("app/")) {
                    onlineWay.append("app/");
                } else if (client.contains("web--") && !onlineWay.toString().contains("web/")) {
                    onlineWay.append("web/");
                }
            }
            String onlineWayStr = onlineWay.substring(0, onlineWay.length() - 1);
            user.setOnlineWay(onlineWayStr);
        }
        userService.saveOrUpdate(user);
    }

    /**
     * 判断是否连接的方法
     *
     * @return
     */
    public static boolean isServerClose(Long userId) {
        if (conns.get(userId).size() == 0) {
            log.info("已断开");
            return true;
        } else {
            log.info("已连接");
            return false;
        }
    }

    /**
     * 发送给所有用户
     *
     * @param noticeInfo
     */
    public void sendMessage(Map<String, Object> noticeInfo) {
        String message = JSONObject.toJSONString(noticeInfo);
        for (Session session1 : NoticeWebsocket.clients.values()) {
            try {
                session1.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据用户id发送给某一个用户
     **/
    public void sendMessageByUserId(Long userId, Map<String, Object> noticeInfo) {
        if (!StringUtils.isEmpty(userId)) {
            String message = JSONObject.toJSONString(noticeInfo);
            Set<String> clientSet = conns.get(userId);
            if (clientSet != null) {
                Iterator<String> iterator = clientSet.iterator();
                while (iterator.hasNext()) {
                    String sid = iterator.next();
                    Session session = clients.get(sid);
                    if (session != null) {
                        try {
                            session.getBasicRemote().sendText(message);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *
     */
    @OnMessage
    public void onMessage(String message) {
        for (Session session1 : NoticeWebsocket.clients.values()) {
            try {
                String message1 = "pong";
                session1.getBasicRemote().sendText(message1);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发生错误时的回调函数
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error) {
        log.info("错误");
        error.printStackTrace();
    }

    @Scheduled(cron = "0 0/30 * * * ?")
    public void updateUserSuperviLength() {
        Set<Long> userIds = conns.keySet();
        if (userIds.size() > 0) {
            List<User> userList = userService.listUsersByIds(userIds);
            for (User user:userList) {
                user.setSuperviLength(user.getSuperviLength() + (System.currentTimeMillis() - user.getLastLoginTime()));
                user.setLastLoginTime(System.currentTimeMillis());
            }
            userService.saveOrUpdateBatch(userList);
        }
    }

}
